package cn.hn.java.summer.db;

import cn.hn.java.summer.constants.Default;
import cn.hn.java.summer.context.ThreadContextManage;
import cn.hn.java.summer.db.builder.FormatResult;
import cn.hn.java.summer.db.builder.SqlBuilder;
import cn.hn.java.summer.db.mapper.SqlGenerator;
import cn.hn.java.summer.db.mapper.SqlMapper;
import cn.hn.java.summer.db.paging.IPagingConverter;
import cn.hn.java.summer.exception.SummerException;
import cn.hn.java.summer.utils.ReflectUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.*;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.util.Assert;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


/**
 * 数据库操作类
 */
public class DbOperator extends JdbcTemplate implements IDbOperator {

	/**
	 * 无属性类型缓存
	 */
	private static final ConcurrentHashMap<Type,Boolean> NoPropertyTypeCache=new ConcurrentHashMap<>();

	/**
	 * 判断类型是否是包含属性的bean
	 * @param type 类型
	 * @return boolean
	 */
	public static <T> boolean hasProperty(Class<T> type){
		if(type==null){
			return false;
		}
		Boolean result=NoPropertyTypeCache.get(type);
		if(result!=null){
			return result;
		}
		if(type.getPackage().getName().contains("java.")){
			return false;
		}
		result= ReflectUtils.hasProperty(type);
		NoPropertyTypeCache.put(type,result);
		return result;
	}

	/**
	 * 分页转换器
	 */
	private IPagingConverter pagingConverter;
	public void setPagingConverter(IPagingConverter pagingConverter) {
		this.pagingConverter = pagingConverter;
	}

	/**
	 * 调用存储过程执行分页查询
	 * @param <T>
	 * @param sql 查询语句
	 * @param rowMapper 行映射器
	 * @param pageSize 分页大小 不设置默认为Default.PAGE_SIZE
	 * @param page 当前页 从1开始
	 * @param args sql参数
	 * @return 实体列表
	 * @throws DataAccessException
	 */
	private <T> List<T> page(String sql,RowMapper<T> rowMapper,Integer pageSize,final Integer page,Object...args){
		
		//取格式化后的sql
		FormatResult rst=pagingConverter.formatSql(sql, args);
		
		//取总记录数
		count(rst.getSql(),rst.getArgs());
		
		//分页
		return pagingConverter.paging(this, rst.getSql(), pageSize, page, rowMapper, rst.getArgs());
	}
	
	/**
	 * 计算查询记录数
	 * @param sql  select xxx from yyy
	 * @param args 参数
	 * @return 记录数
	 */
	private int count(String sql,Object...args){
		//组织sql语句
		sql=SqlBuilder.formCountSql(sql);
		Integer total = getNoPropertyObject(Integer.class,sql,args);
		//保存总记录数
		ThreadContextManage.setAttribute(Default.PAGING_TOTAL_IN_REQUEST_KEY,total);
		return total;
	}	
	
	/**
	 * 调用存储过程执行分页查询,以args为查询条件
	 * @param <T>
	 * @param sql
	 * @param cls
	 * @param pageSize
	 * @param page
	 * @param args
	 * @return
	 */
	public <T> List<T> list(final String sql, Class<T> cls,Integer pageSize,final Integer page,Object...args) {
		return this.page(sql,getListRowMapper(cls),pageSize,page,args);
	}
	
	/**
	 * 取列表
	 * @param <T>
	 * @param sql
	 * @param elementType  
	 * 该值为实体bean类，字段名与属性名对应
	 * @param args
	 * 可以是包含参数的对象，也可以是单个参数列
	 * @return
	 */
	public <T> List<T> list(String sql,Class<T> elementType, Object...args){
		FormatResult rst=SqlBuilder.format(sql, args);
		return this.query(rst.getSql(),rst.getArgs(),getListRowMapper(elementType));
	}
	
	/**
	 * 取列表行映射器
	 * @param <T>
	 * @param elementType
	 * @return
	 */
	private <T> RowMapper<T> getListRowMapper(Class<T> elementType){
		//单列，String和一些原始类型
		if(
				elementType==String.class ||
				elementType==Integer.class ||
				elementType==Short.class ||
				elementType==Byte.class ||
				elementType==Float.class ||
				elementType==Double.class ||
				elementType==Long.class ||
				elementType==Date.class
		){
			//取第一列值
			return (ResultSet rs, int rowNum)->(T)rs.getObject(1);
		}
		//多列，其它实体类
		return new BeanPropertyRowMapper<>(elementType);
	}

	/**
	 * 强制取所有数据不分页的方法
	 * @param <T>
	 * @param sql
	 * @param elementType
	 * @param args
	 * @return
	 * @throws SummerException
	 */
	public <T> List<T> all(String sql,Class<T> elementType, Object...args){
		return this.list(sql, elementType, args);
	}
	
	/**
	 * 查询单个对象值
	 * 级联查询：当参数sqlId格式为a.b时，a为父级sqlId，b为type中对应的属性名，a.b为子sqlId
	 * 查询时，先查询出父级sqlId为a的bean，再用子级sqlId为a.b查出对应的bean或list设置给父级bean中的b属性
	 * @param <T>
	 * @param type
	 * @param sqlId
	 * @param args
	 * @return
	 */
	public <T> T get(String sqlId, Class<T> type, Object... args){
		//取无声明属性的对象
		if(!hasProperty(type)){
			FormatResult rst=SqlBuilder.format(sqlId,args);
			return getNoPropertyObject(type,rst.getSql(),rst.getArgs());
		}
		//有属性的为bean
		FormatResult fr=SqlBuilder.getSqlById(sqlId);
		String parentSqlId=sqlId;
		int symbolIndex=sqlId.indexOf(SqlBuilder.CASCADE_SYMBOL);
		//sqlId与sql不一致则为配置的sql
		if (fr.getSql()!=null && !sqlId.equals(fr.getSql()) && symbolIndex>0) {
			//获取父级sqlId，原sqlId则成为子sqlId
			//即当参数sqlId格式为a<-b时，a为父级sqlId，b为type中对应的属性名，a<-b为子sqlId
			//查询时，先查询出父级sqlId为a的bean，再用子级sqlId为a<-b查出对应的bean或list设置给父级bean中的b属性
			parentSqlId=sqlId.substring(0,symbolIndex);
			//先查父级bean
			FormatResult rst=SqlBuilder.format(parentSqlId,args);
			T parentBean=getObject(type,rst.getSql(),rst.getArgs());
			if(parentBean==null){
				return null;
			}
			//级联查询属性
			//执行子sql
			String propName=sqlId.substring(symbolIndex+SqlBuilder.CASCADE_SYMBOL.length());
			//获取属性
			Field prop=ReflectUtils.getField(type,propName);
			if(prop==null){
				return parentBean;
			}
			//根据属性类型查询结果
			Object propVal=getObjectByPropType(sqlId,prop.getGenericType(),args);
			//设置属性值
			ReflectUtils.setFieldValue(parentBean,propName,propVal);
			return parentBean;
		}

		//非级联查询
		FormatResult rst=SqlBuilder.format(parentSqlId,args);
		return getObject(type,rst.getSql(),rst.getArgs());
	}

	/**
	 * 获取通过属性类型查询到对象
	 * @param sqlId
	 * @param type
	 * @param args
	 * @return
	 */
	private Object getObjectByPropType(String sqlId, Type type, Object[] args){
		//属性值
		Object propVal;
		//类型的class
		Class typeClass=ReflectUtils.getTypeClass(type);
		if(typeClass == List.class){
			Class[] genericClasses=ReflectUtils.getActualTypeArguments(type);
			if(genericClasses.length>0){
				//属性类型为列表，查询列表结果
				propVal=all(sqlId,genericClasses[0],args);
			}else{
				propVal=new ArrayList<>();
			}
		}else{
			//取单个需要先取得sql
			FormatResult rst=SqlBuilder.format(sqlId, args);
			//查询bean结果
			propVal=getObject(typeClass,rst.getSql(),rst.getArgs());
		}
		return propVal;
	}
	
	
	/**
	 * 取无声明属性的对象，如String,int...
	 * @param <T>
	 * @param type
	 * @param sql
	 * @param args
	 * @return
	 */
	private <T> T getNoPropertyObject(Class<T> type, final String sql, Object... args){
		try{
			return queryForObject(
				sql, 
				args, 
				getSingleColumnRowMapper(type)
			 );
		}catch(EmptyResultDataAccessException ex){
			return null;
		}
	}
	
	/**
	 * 将单行结果转换为实体bean，字段别名与属性名对应
	 * @param <T>
	 * @param sql
	 * @param cls
	 * @param args
	 * @return
	 */
	private <T> T getObject(Class<T> cls,final String sql,Object... args){
		List<T> list= this.query(sql, new BeanPropertyRowMapper<>(cls),args);
		if(list.size()>0){
			return list.get(0);
		}
		return null;
	}		
	
	
	//==========================================================================================
	
	
	/**
	 * 执行sql语句
	 */
	public void execute(String sql) {
		super.execute(SqlBuilder.format(sql));
	}

	/**
	 * 执行添加、修改、删除sql
	 * 注：执行insert时若要返回自增id，请使用insert方法
	 */
	public int update(String sql, Object... args) {
		FormatResult rst=SqlBuilder.format(sql,args);
		return super.update(rst.getSql(),
				rst.getArgs());
	}

	/**
	 * 执行带自增id的添加语句时，提供获取自增的id值
	 * @param sql sql语句
	 * @param generatedKeyHolder 存储返回的自增id
	 * @param args 参数列表
	 * @return 影响记录数
	 * @throws DataAccessException
	 */
	private int update(String sql, KeyHolder generatedKeyHolder, Object... args)
			throws DataAccessException {
		Assert.notNull(sql, "SQL must not be null");
		Assert.notNull(generatedKeyHolder, "KeyHolder must not be null");
		logger.debug("Executing prepared SQL update");
		PreparedStatementSetter pss=new ArgumentPreparedStatementSetter(args);
		return execute(new AutoGenerateIdPreparedStatementCreator(sql), (ps)->{
			try {
				if (pss != null) {
					pss.setValues(ps);
				}
				int rows = ps.executeUpdate();

				List<Map<String, Object>> generatedKeys = generatedKeyHolder.getKeyList();
				generatedKeys.clear();
				ResultSet keys = ps.getGeneratedKeys();
				if (keys != null) {
					try {
						RowMapperResultSetExtractor<Map<String, Object>> rse =
								new RowMapperResultSetExtractor<>(getColumnMapRowMapper(), 1);
						generatedKeys.addAll(rse.extractData(keys));
					}
					finally {
						JdbcUtils.closeResultSet(keys);
					}
				}
				if (logger.isDebugEnabled()) {
					logger.debug("SQL update affected " + rows + " rows and returned " + generatedKeys.size() + " keys");
				}
				return rows;
			}
			finally {
				if (pss instanceof ParameterDisposer) {
					((ParameterDisposer) pss).cleanupParameters();
				}
			}
		});
	}

	/**
	 * 执行批量添加、修改、删除操作
	 * @throws SummerException
	 */
	public <T> int[] batchUpdate(String sql, List<T> batchArgs,
			IBatchArgMapper mapper) throws SummerException {
		FormatResult rst=SqlBuilder.format(sql,batchArgs.get(0));
		List<Object[]> args=new ArrayList<>();
		for(T t:batchArgs){
			args.add(mapper.getArgs(t));
		}
		return super.batchUpdate(rst.getSql(), args);
	}

	//===================================不用传sql的部分==================================

	/**
	 * 取单表列表
	 *
	 * @param elementType 实体类
	 * @param args        查询参数
	 * @return 实体列表
	 */
	@Override
	public <T> List<T> list(Class<T> elementType, Object... args) {
		return list(SqlGenerator.genSelectStatement(elementType,null),elementType,args);
	}

	/**
	 * 取单表列表
	 * @param elementType 实体类
	 * @param orderBy 排序
	 * @param args 查询参数
	 * @param <T> 实体类型
	 * @return 实体列表
	 */
	public <T> List<T> list(Class<T> elementType, OrderBy orderBy, Object...args){
		return list(SqlGenerator.genSelectStatement(elementType,orderBy),elementType,args);
	}

	/**
	 * 分页取单表列表
	 *
	 * @param elementType 实体类
	 * @param page        第几页（从1开始）
	 * @param pageSize    分页大小
	 * @param orderBy     排序
	 * @param args        查询参数
	 * @return 实体列表
	 */
	public <T> List<T> list(Class<T> elementType, Integer pageSize, Integer page, OrderBy orderBy, Object... args) {
		return list(SqlGenerator.genSelectStatement(elementType, orderBy),elementType,pageSize,page,args);
	}

	/**
	 * 单表-强制取所有数据不分页的方法
	 *
	 * @param elementType 实体类
	 * @param args        查询参数
	 * @return 实体列表
	 */
	@Override
	public <T> List<T> all(Class<T> elementType, Object... args) {
		return all(SqlGenerator.genSelectStatement(elementType,null), elementType, args);
	}

	/**
	 * 单表-查询单个对象值
	 * @param type 实体类
	 * @param args 查询参数
	 * @return 实体列表
	 */
	@Override
	public <T> T get(Class<T> type, Object... args) {
		return get(SqlGenerator.genSelectStatement(type,null),type,args);
	}

	/**
	 * 按主键更新
	 *
	 * @param bean 实体
	 * @return 更新记录数
	 */
	@Override
	public <T> int update(T bean) {
		return update(SqlGenerator.genUpdateStatement(bean.getClass(),null),bean);
	}

	/**
	 * 按条件更新
	 *
	 * @param bean 实体
	 * @param condition 条件
	 * @return 更新记录数
	 */
	public <T> int update(T bean, ConditionEntity condition) {
		return update(SqlGenerator.genUpdateStatement(bean.getClass(),condition),new Object[]{bean,condition});
	}

	/**
	 * 插入
	 *
	 * @param bean 实体
	 * @return 插入记录数
	 */
	@Override
	public <T> int insert(T bean) {
		//取自增的属性名
		SqlMapper.ColField autoIncreaseField= SqlMapper.getAutoIncreasePropName(bean.getClass());
		boolean hasAutoIncrease=autoIncreaseField!=null;
		//没有自增列的，直接执行
		if(!hasAutoIncrease){
			//先取得生成的sql
			String sql=SqlGenerator.genInsertStatement(bean);
			return update(sql,bean);
		}
		//带自增id的表插入操作
		return insertWithAutoIncreaseId(bean,autoIncreaseField);
	}

	/**
	 * 带自增id的表插入操作
	 * @param bean 实体
	 * @param field 自增列
	 * @param <T> 实体类型
	 * @return 插入记录数
	 */
	private <T> int insertWithAutoIncreaseId(T bean, SqlMapper.ColField field){
		//有自增列，需要将自增id值设置回对应的属性
		KeyHolder keyHolder= new GeneratedKeyHolder();
		//生成sql
		String sql=SqlGenerator.genInsertStatement(bean);
		FormatResult rst=SqlBuilder.format(sql,bean);
		//执行sql
		int result=update(rst.getSql(), keyHolder, rst.getArgs());
		//获取自增id值
		Object idValue;
		Number keyValue=keyHolder.getKey();
		if(keyValue==null){
			return result;
		}
		//自增id值类型转换
		if(field.propType == Short.class){
			idValue=keyValue.shortValue();
		}else if(field.propType == Long.class){
			idValue=keyValue.longValue();
		}else if(field.propType == Byte.class){
			idValue=keyValue.byteValue();
		}else{
			idValue=keyValue.intValue();
		}
		//更新到对应的属性中
		ReflectUtils.setFieldValue(bean,field.propName,idValue);
		return result;
	}

	/**
	 * 按主键删除
	 *
	 * @param bean 实体
	 * @return 删除记录数
	 */
	@Override
	public <T> int delete(T bean) {
		return update(SqlGenerator.genDeleteStatement(bean.getClass(),ConditionEntity.fromObj(bean)), bean);
	}

	/**
	 * 自动递增Id的预准备语句创建器
	 */
	class AutoGenerateIdPreparedStatementCreator implements PreparedStatementCreator, SqlProvider {

		private final String sql;

		public AutoGenerateIdPreparedStatementCreator(String sql) {
			Assert.notNull(sql, "SQL must not be null");
			this.sql = sql;
		}

		@Override
		public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
			return con.prepareStatement(this.sql, PreparedStatement.RETURN_GENERATED_KEYS);
		}

		@Override
		public String getSql() {
			return this.sql;
		}
	}
}
